package anonymouschat.client.frames;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;


import anonymouschat.client.Client;
import anonymouschat.client.ClientInConnectionThread;
import anonymouschat.messages.AESkeyMessage;
import anonymouschat.messages.Message;
import anonymouschat.messages.TextMessage;
import anonymouschat.messages.clientrequests.ChatroomChangeRequest;
import anonymouschat.messages.clientrequests.UserLeaveRequest;
import anonymouschat.messages.clientrequests.UserLoginRequest;
import anonymouschat.messages.clientrequests.encryptedMessageWrapperMessage;
import anonymouschat.messages.serverresponses.ChatroomMemberResponse;
import anonymouschat.service.serverEntry;
import anonymouschat.utils.ConstantVariables;
import anonymouschat.utils.LocalFileOps;


/**
 * A chat room panel.
 * 
 * TODO:
 * 
 * Dump chat log regularly to local file.
 * 
 * When user clicks on change chat room option item, get a list of existing chat
 * rooms, by sending a ChatRoomListRequest and wait for ChatRoomListResponse.
 * 
 * When user clicks on goto chat room button, create a new chat frame and close
 * the previous one. Dump chat log.
 * 
 * @author hb
 * 
 */
public class ChatFrame extends JFrame implements ActionListener
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	Client client;

	static ObjectOutputStream out;

	Socket clientSocket;

	private static final int WIDTH = 800;

	private static final int HEIGHT = 500;

	String chatName;

	/*
	 * put temporary chat log into this. dumpChatLog empty this, and outputs
	 * into file.
	 */
	private HashMap<String, ArrayList<String>> tempChatLog;

	// chatInfoPanel
	private JPanel chatInfoPanel;

	private JPanel changeChatPanel;

	private JButton changeChatButton;

	private JTextField changeChatField;

	// msgPanel
	private JPanel msgPanel;

	private JLabel chatLabel;

	private JPanel namePanel;

	private JTextField dstField;

	private JScrollPane msgScr;

	private JTextArea msgArea;

	private JButton msgButton;

	private ChatMemberPanel chatMemberPanel;

	private JScrollPane textScr;

	private static JTextArea textArea;

	/**
	 * Constructor by client instance and chat room name
	 * 
	 * @param client
	 * @param chatName
	 */
	public ChatFrame( Client client, String chatName ) {
		this.client = client;
		this.chatName = chatName;
		this.tempChatLog = new HashMap<String, ArrayList<String>>();

		// main frame
		setTitle( "User : " + client.getIdentity() );
		setSize( WIDTH, HEIGHT );
		setVisible( true );
		setLayout( new BorderLayout() );

		// chatInfoPanel
		changeChatPanel = new JPanel();
		changeChatPanel.add( new JLabel( "Go to Chatroom : " ) );
		changeChatField = new JTextField( 10 );
		changeChatPanel.add( changeChatField );
		changeChatButton = new JButton( "Go" );
		changeChatPanel.add( changeChatButton );

		chatInfoPanel = new JPanel();
		chatInfoPanel.setLayout( new GridLayout( 1, 2 ) );
		chatLabel = new JLabel( "You are chatting in chatroom : '" + chatName
				+ "'" );
		chatLabel.setHorizontalAlignment( JLabel.CENTER );
		chatInfoPanel.add( chatLabel );
		chatInfoPanel.add( changeChatPanel );
		add( chatInfoPanel, BorderLayout.NORTH );

		chatMemberPanel = new ChatMemberPanel( this, chatName);
		getContentPane().add( chatMemberPanel, BorderLayout.EAST );

		// msgPanel
		namePanel = new JPanel();
		namePanel.setLayout( new GridLayout( 2, 1 ) );
		namePanel.add( new JLabel( "You -> " ) );
		dstField = new JTextField( 10 );
		namePanel.add( dstField );

		msgArea = new JTextArea();
		msgScr = new JScrollPane( msgArea );

		msgButton = new JButton( "SEND" );
		msgButton.addActionListener( this );

		msgPanel = new JPanel();
		msgPanel.setLayout( new BorderLayout() );
		msgPanel.add( namePanel, BorderLayout.WEST );
		msgPanel.add( msgButton, BorderLayout.EAST );
		msgPanel.add( msgScr, BorderLayout.CENTER );
		add( msgPanel, BorderLayout.SOUTH );

		// textArea
		textArea = new JTextArea();
		textArea.setEditable( false );
		textScr = new JScrollPane( textArea );
		add( textScr );
		addWindowListener( new WindowAdapter()
		{
			public void windowClosing( WindowEvent e ) {
				dumpChatLog();
				leaveSystem();
			}
		} );


		try {
			establishAnonymousRouting();
		} catch (Exception e) {
			System.err.println("ERROR encountered while trying to establish annonymous routing: " + e.getLocalizedMessage());
			e.printStackTrace();
		}
		sendUserLoginRequest();

		// need to be placed in the right place, and check that there are not multiple private chatting window for same user
		// also need to deal with the log. The current solution is that do nothing when private chat ends. only save file when exiting chat room.
		//new OneOnOneChatFrame("Owner","TargetUser", tempChatLog, out);
	}

	private void establishAnonymousRouting() throws Exception {
		serverEntry randomServer;
		for(int i=0; i<ConstantVariables.numServerHops; i++)
		{
			randomServer = Client.ListOfAvailableServers.getRandomServer();
			if (Client.serverChain.size() == 0) //we need to make the initial server connection, after that we can simply pass messages of to this server
			{
				clientSocket = new Socket( randomServer.serverIP, randomServer.serverPort );
				showMessage( "Connected to port " + randomServer.serverPort );
				showError( "Could not connect to server on port " + randomServer.serverPort );
				ClientInConnectionThread t = new ClientInConnectionThread( this,clientSocket );
				t.start();
				out = t.getOutputStream();			
			} 
			
			client.serverChain.add(randomServer);
			AESkeyMessage k = new AESkeyMessage();
			k.SharedAESKey = generateAESkey();
			sendMessage(k);
			randomServer.setSymmetricKey(k.SharedAESKey);
		}
	}
	
	public static SecretKey generateAESkey() throws NoSuchAlgorithmException
	{
		 KeyGenerator kgen = KeyGenerator.getInstance("AES");
	     kgen.init(128);
	     return kgen.generateKey();
	}

	public static void sendMessage(Message msg) throws IOException
	{
		try{
			encryptedMessageWrapperMessage wrapper;
			Cipher engine;
			for(int i =0; i<Client.serverChain.size(); i++)
			{
				System.out.print("Encrypting message to send (");
			}
			for(int i = 0; i<Client.serverChain.size(); i++)
			{
				if(i > 0)
					((encryptedMessageWrapperMessage)msg).setNextHop(Client.serverChain.get(i).serverName);
				System.out.print(Client.serverChain.get(i).serverName + ")");
				if (Client.serverChain.get(i).hasSymmetricKey())
					wrapper = new encryptedMessageWrapperMessage(Client.serverChain.get(i).getSymmetricCipherEngine(), msg, false);
				else
					wrapper = new encryptedMessageWrapperMessage(Client.serverChain.get(i).getAsymmetricCipherEngine(), msg, true);
				wrapper.setConnectionNumber(1);
				//TODO this next line was set by nick, the evil monkey  WHat do we do with it?
				//wrapper.setUserLogin(login);
				msg = wrapper;
			}
			out.writeObject(msg);
			out.flush();
		} catch( Exception e)
		{
			System.err.println("Error encoutered while attempting to send message client->server " + e.getLocalizedMessage());
			e.printStackTrace();
		}
	}

	/**
	 * User first login request
	 */
	private void sendUserLoginRequest() {
		UserLoginRequest req = new UserLoginRequest( client.getIdentity().getUserID() );
		try {
			sendMessage( req );
		} catch( IOException e ) {
			showError( "Unable to send UserLoginRequest to server" );
			e.printStackTrace();
		}
	}

	/**
	 * User leaves system.
	 */
	protected void leaveSystem() {
		UserLeaveRequest msg = new UserLeaveRequest( client.getIdentity().getUserID(),
				this.chatName );
		try {
			out.writeObject( msg );
		} catch( IOException e1 ) {
			System.err
			.println( "Error occured when sending UserLeaveRequest object to server" );
			e1.printStackTrace();
		}
	}

	/**
	 * Save tempChatLog to file.
	 */
	private void dumpChatLog() {
		for( Map.Entry<String, ArrayList<String>> entry : tempChatLog
				.entrySet() ) {
			String userID = entry.getKey();
			ArrayList<String> msgList = entry.getValue();
			for( String s : msgList ) {
				LocalFileOps.appendChatLog( userID, s );
			}
		}
		tempChatLog.clear();
	}

	@Override
	public void actionPerformed( ActionEvent e ) {
		String s = e.getActionCommand();
		if( "SEND".equals( s ) ) {
			sendTextMsg( dstField.getText(), msgArea.getText() );
			msgArea.setText( "" );
		} else if( "GO".equals( s ) ) {
			dumpChatLog();
			sendSwitchChatroomRequest( changeChatField.getText() );
		}
	}

	/**
	 * action performed when a change chat room button is clicked.
	 * 
	 * @param text
	 */
	private void sendSwitchChatroomRequest( String dstChatName ) {
		ChatroomChangeRequest msg = new ChatroomChangeRequest(
				client.getIdentity().getUserID(), this.chatName, dstChatName );
		try {
			sendMessage( msg );
		} catch( IOException e1 ) {
			System.err
			.println( "Error occured when sending ChatroomChangeRequest object to server" );
			e1.printStackTrace();
		}
	}

	/**
	 * action performed when a send msg button is clicked.
	 * 
	 * 
	 * @param dst
	 *            if dst is empty, send chat room message
	 * @param data
	 */
	private void sendTextMsg( String dst, String data ) {
		TextMessage text = new TextMessage();
		text.srcUser = client.getIdentity().getUserID();
		if( dst.equals( "" ) ) {
			text.destinationChatroom = this.chatName;
			textArea.append( "You say to ALL: \n" );
		} else {
			text.destinationUser = dst;
			String tempData = "You ->" + dst + ": " + data + "\n";
			textArea.append( tempData );
			if( tempChatLog.containsKey( dst ) ) {
				tempChatLog.get( dst ).add( tempData );
			} else {
				tempChatLog.put( dst, new ArrayList<String>() );
				tempChatLog.get( dst ).add( tempData );
			}
		}
		text.encryptedData = data;
		try {
			sendMessage( text );
		} catch( IOException e1 ) {
			showError( "Error occured when sending TextMessage object to server" );
			e1.printStackTrace();
		}
	}

	/**
	 * show chat messages that clientInConnectionThread receives. save the
	 * message in temporary chat record.
	 * 
	 * @param msg
	 */
	public void showTextMessage( TextMessage msg ) {
		String userID = msg.srcUser;
		String tempData = msg.srcUser + " -> you: \n" + msg.encryptedData
		+ "\n";
		textArea.append( tempData );
		if( tempChatLog.containsKey( userID ) ) {
			tempChatLog.get( userID ).add( tempData );
		} else {
			tempChatLog.put( userID, new ArrayList<String>() );
			tempChatLog.get( userID ).add( tempData );
		}
	}

	/**
	 * update chat room member list.
	 * 
	 * @param receivedMessage
	 */
	public void updateChatMemberPanel( ChatroomMemberResponse receivedMessage ) {
		showMessage( "Updated list of users for chatroom "
				+ receivedMessage.getChatroomName() );
		chatMemberPanel.setMemberList( receivedMessage.getMembers() );
	}

	/**
	 * Display an error to the user via the text area
	 */
	public void showError( String error ) {
		textArea.append( "*****ERROR: " + error + " ******\n" );
	}

	/**
	 * Display a message to the user via the text area
	 */
	public void showMessage( String msg ) {
		textArea.append( msg );
	}

	/**
	 * add message receiver in dst field.
	 * 
	 * @param dst
	 */
	public void addChatDst( String dst ) {
		dstField.setText( dst );
	}

	/**
	 * successfully changed chatroom
	 * 
	 * @param dstChatroomName
	 */
	public void switchChatroomOk( String dstChatroomName ) {
		chatName = dstChatroomName;
		chatLabel.setText( "You are chatting in chatroom : '" + chatName + "'" );
		dstField.setText( "" );
		textArea.setText( "" );
	}
}
